package io.github.wanggit.antrpc.console.web;

import io.github.wanggit.antrpc.commons.bean.ConfigItemData;
import io.github.wanggit.antrpc.commons.lb.LoadBalancerDTO;
import io.github.wanggit.antrpc.commons.utils.ApplicationNameUtil;
import io.github.wanggit.antrpc.commons.zookeeper.NodeUnregDTO;
import io.github.wanggit.antrpc.console.service.AccountService;
import io.github.wanggit.antrpc.console.service.CallLogService;
import io.github.wanggit.antrpc.console.service.ConfigService;
import io.github.wanggit.antrpc.console.service.InterfaceService;
import io.github.wanggit.antrpc.console.service.dto.InterfaceCalledDetailsDTO;
import io.github.wanggit.antrpc.console.web.constants.WebConstants;
import io.github.wanggit.antrpc.console.web.dto.TimeRangeDTO;
import io.github.wanggit.antrpc.console.web.utils.StringToConfigMapUtil;
import io.github.wanggit.antrpc.console.web.vo.*;
import io.github.wanggit.antrpc.console.zookeeper.IAllNodeContainer;
import io.github.wanggit.antrpc.console.zookeeper.IInterfaceContainer;
import io.github.wanggit.antrpc.console.zookeeper.IUnregNodeContainer;
import io.github.wanggit.antrpc.console.zookeeper.InterfaceDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.stream.Collectors;

@Controller
@RequestMapping
public class PageController {

    @Autowired private IInterfaceContainer interfaceContainer;

    @Autowired private IAllNodeContainer allNodeContainer;

    @Autowired private IUnregNodeContainer unregNodeContainer;

    @Autowired private InterfaceService interfaceService;

    @Autowired private CallLogService callLogService;

    @Autowired private AccountService accountService;

    @Autowired private ConfigService configService;

    @Value("${antrpc.zk.node.prefixs}")
    private String nodePrefixs;

    /**
     * 配置的环境列表
     *
     * @param keyword keyword
     * @param model model
     * @return viewName
     */
    @RequestMapping("configs")
    public String configs(
            @RequestParam(value = "keyword", required = false) String keyword, Model model) {
        List<String> envs = configService.findAllEnvs(keyword);
        model.addAttribute("envs", envs);
        return "configs";
    }

    /**
     * 某环境下的配置空间列表
     *
     * @param keyword keyword
     * @param envName envName
     * @param model model
     * @return viewName
     */
    @RequestMapping("configSpaces")
    public String configSpaces(
            @RequestParam(value = "keyword", required = false) String keyword,
            @RequestParam("envName") String envName,
            Model model) {
        List<String> datas = configService.findAllSpaces(keyword, envName);
        model.addAttribute("datas", datas);
        model.addAttribute("keyword", keyword);
        model.addAttribute("envName", envName);
        return "config_spaces";
    }

    /**
     * 某环境某空间下的所有key列表
     *
     * @param keyword key keyword
     * @param envName env name
     * @param spaceName space name
     * @param model model
     * @return viewName
     */
    @RequestMapping("configKeys")
    public String configKeys(
            @RequestParam(value = "keyword", required = false) String keyword,
            @RequestParam("envName") String envName,
            @RequestParam("spaceName") String spaceName,
            Model model) {
        List<ConfigVO> configVOS = configService.findAllKeys(keyword, envName, spaceName);
        model.addAttribute("datas", configVOS);
        model.addAttribute("keyword", keyword);
        model.addAttribute("envName", envName);
        model.addAttribute("spaceName", spaceName);
        return "config_keys";
    }

    /**
     * 某一个配置项的值
     *
     * @param envName env name
     * @param spaceName space name
     * @param key key
     * @param model model
     * @return viewName
     */
    @RequestMapping("configKeyDetails")
    public String configKeyDetails(
            @RequestParam("envName") String envName,
            @RequestParam("spaceName") String spaceName,
            @RequestParam(value = "key", required = false) String key,
            @RequestParam("opr") String opr,
            Model model) {
        ConfigItemData configItemData = new ConfigItemData();
        if (Objects.equals(opr, "edit") || Objects.equals(opr, "view")) {
            configItemData = configService.findConfigItemData(envName, spaceName, key);
        }
        model.addAttribute("configItemData", configItemData);
        List<String> subscribes = configService.findConfigSubscribeNodes(envName, spaceName);
        model.addAttribute("subscribes", subscribes);
        model.addAttribute("grayReleases", configItemData.getGrayReleases());
        model.addAttribute("envName", envName);
        model.addAttribute("spaceName", spaceName);
        model.addAttribute("key", key);
        model.addAttribute("opr", opr);
        return "config_values";
    }

    @RequestMapping("configFuzzyQuery")
    public String configFuzzyQuery(@RequestParam("pattern") String pattern, Model model) {
        List<ConfigVO> configVOS = configService.configFuzzyQuery(pattern);
        model.addAttribute("configs", configVOS);
        return "config_fuzzy";
    }

    @RequestMapping("batchCreateConfigs")
    public String batchCreateConfigs(
            @RequestParam("envName") String envName,
            @RequestParam("spaceName") String spaceName,
            Model model) {
        model.addAttribute("envName", envName);
        model.addAttribute("spaceName", spaceName);
        return "config_batch";
    }

    @RequestMapping("saveBatchCreateConfigs")
    public String saveBatchCreateConfigs(
            @RequestParam("envName") String envName,
            @RequestParam("spaceName") String spaceName,
            @RequestParam("type") String type,
            @RequestParam("content") String content,
            Model model) {
        Map<String, String> configMap = null;
        if (Objects.equals("prop", type)) {
            configMap = StringToConfigMapUtil.propStrToMap(content);
        } else if (Objects.equals("yaml", type)) {
            configMap = StringToConfigMapUtil.yamlStrToMap(content);
        }
        if (null != configMap && !configMap.isEmpty()) {
            configService.saveOrUpdateKeys(envName, spaceName, configMap);
        }
        return "redirect:/configKeys?envName=" + envName + "&spaceName=" + spaceName;
    }

    @RequestMapping(method = RequestMethod.POST, value = "login_submit")
    public String submitLogin(
            @RequestParam("userName") String userName,
            @RequestParam("password") String password,
            HttpServletResponse response,
            RedirectAttributes redirectAttributes) {
        String token = accountService.checkAccount(userName, password);
        if (null != token) {
            Cookie cookie = new Cookie(WebConstants.TOKEN, token);
            cookie.setHttpOnly(true);
            cookie.setPath("/");
            // 24 hours
            cookie.setMaxAge(86400);
            response.addCookie(cookie);
            return "redirect:/";
        }
        redirectAttributes.addFlashAttribute("error", "用户名或密码错误");
        return "redirect:/login";
    }

    @RequestMapping("login")
    public String login() {
        return "login";
    }

    @RequestMapping("call_graphs")
    public String callGraphs(
            @RequestParam(value = "sn") String serialNumber,
            @RequestParam(value = "rid") String requestId,
            Model model) {
        model.addAttribute("serialNumber", serialNumber);
        model.addAttribute("requestId", requestId);
        return "call_graphs";
    }

    @RequestMapping("logs")
    public String logs(
            Model model,
            @RequestParam(value = "keyword", required = false) String keyword,
            @RequestParam(value = "range", required = false) String range,
            @RequestParam(value = "page", required = false, defaultValue = "1") int page) {
        TimeRangeDTO timeRangeDTO = TimeRangeDTO.buildOf(range);
        model.addAttribute("datas", callLogService.query(keyword, timeRangeDTO.getRange(), page));
        model.addAttribute("args", new LogsArgsVO(keyword, timeRangeDTO.getRange()));
        return "call_logs";
    }

    @RequestMapping("graph")
    public String graph(
            @RequestParam(value = "keyword", required = false) String keyword, Model model) {
        model.addAttribute("keyword", keyword);
        return "apps_graph";
    }

    @RequestMapping("method_stat")
    public String methodStat(
            @RequestParam("clazz") String className,
            @RequestParam("host") String appNameHostPort,
            @RequestParam(value = "ranges", required = false) String ranges,
            @RequestParam(value = "r", required = false) String refresh,
            Model model) {
        TimeRangeDTO timeRangeDTO = TimeRangeDTO.buildOf(ranges);
        if (null != timeRangeDTO) {
            List<InterfaceCalledDetailsDTO> calledDetailsDTOS =
                    interfaceService.queryInterfaceCalledDetails(
                            className,
                            appNameHostPort,
                            timeRangeDTO.getStart(),
                            timeRangeDTO.getEnd());
            model.addAttribute("details", calledDetailsDTOS);
            MethodStatArgsVO methodStatArgsVO =
                    new MethodStatArgsVO(className, appNameHostPort, timeRangeDTO);
            methodStatArgsVO.setRefresh(refresh);
            model.addAttribute("args", methodStatArgsVO);
        }
        return "method_stat";
    }

    @RequestMapping("method_limit")
    public String methodLimit(
            @RequestParam("clazz") String className,
            @RequestParam("method") String humanFullMethodName,
            Model model) {
        List<MethodLimitVO> providers =
                interfaceContainer.findByInterfaceName(className).stream()
                        .map(
                                it -> {
                                    MethodLimitVO methodLimitVO = new MethodLimitVO();
                                    methodLimitVO.setAppNameAndHostInfo(
                                            it.getAppName() + "@" + it.getHost());
                                    it.getInterfaceMethodDTOS().stream()
                                            .filter(
                                                    mb -> {
                                                        mb.generateFullName();
                                                        return Objects.equals(
                                                                humanFullMethodName,
                                                                mb.getHumanFullName());
                                                    })
                                            .findFirst()
                                            .ifPresent(
                                                    mb -> {
                                                        methodLimitVO.setLimit(mb.getLimit());
                                                        methodLimitVO.setDurationInSeconds(
                                                                mb.getDurationInSeconds());
                                                    });
                                    return methodLimitVO;
                                })
                        .collect(Collectors.toList());
        List<MethodLimitVO> consumers =
                interfaceService.findConsumersByInterfaceName(className, humanFullMethodName);
        model.addAttribute("providers", providers);
        model.addAttribute("consumers", consumers);
        model.addAttribute("className", className);
        model.addAttribute("humanFullMethodName", humanFullMethodName);
        return "method_limit";
    }

    @RequestMapping("interface_weights")
    public String interfaceWeights(
            @RequestParam("className") String className,
            @RequestParam("host") String appNameHostPort,
            Model model) {
        String hostAndPort = ApplicationNameUtil.getHost(appNameHostPort).getHostInfo();
        model.addAttribute("className", className);
        model.addAttribute("host", appNameHostPort);
        LoadBalancerDTO loadBalancerDTO = interfaceService.queryLoadBalancer(className);
        List<NodeUnregDTO> nodeUnregDTOs = unregNodeContainer.findNodeUnregDTOs(className);
        if (null != loadBalancerDTO) {
            Map<String, Integer> weights = loadBalancerDTO.getWeights();
            if (null != weights) {
                model.addAttribute("weightsValue", weights.getOrDefault(hostAndPort, 1));
            }
        }

        List<NodeUnregDTO> unregDTOS =
                nodeUnregDTOs.stream()
                        .filter(it -> Objects.equals(it.getHost(), hostAndPort))
                        .collect(Collectors.toList());
        if (!unregDTOS.isEmpty()) {
            model.addAttribute("weightsValue", 0);
        }

        if (!model.containsAttribute("weightsValue")) {
            model.addAttribute("weightsValue", 1);
        }
        return "interface_weights";
    }

    /**
     * zookeeper节点列表
     *
     * @param model model
     * @return zookeeper.html
     */
    @RequestMapping("zk")
    public String zookeeper(
            Model model,
            @RequestParam(value = "active", required = false) String active,
            @RequestParam(value = "keyword", required = false) String keyword) {
        String[] nodePrefixArr =
                null == nodePrefixs ? new String[] {} : nodePrefixs.split("\\s*,\\s*");
        List<String> prefixs =
                Arrays.stream(nodePrefixArr)
                        .map(it -> it.replaceFirst("/", ""))
                        .collect(Collectors.toList());
        String finalActive = StringUtils.hasText(active) ? active : prefixs.get(0);
        model.addAttribute("prefixs", prefixs);
        model.addAttribute("active", finalActive);
        List<ZookeeperNodeVO> zookeeperNodeVOS = allNodeContainer.findAll(null);
        model.addAttribute(
                "datas",
                zookeeperNodeVOS.stream()
                        .filter(
                                it ->
                                        it.getPath().startsWith("/" + finalActive)
                                                && (null == keyword
                                                        || it.getPath()
                                                                .toLowerCase()
                                                                .contains(keyword.toLowerCase())))
                        .sorted(Comparator.comparing(ZookeeperNodeVO::getPath))
                        .collect(Collectors.toList()));
        return "zookeeper";
    }

    @RequestMapping("details")
    public String details(@RequestParam("clazz") String className, Model model) {
        InterfaceDetails interfaceDetails = interfaceService.collectInterfaceDetails(className);
        model.addAttribute("details", interfaceDetails);
        return "details";
    }

    /**
     * 方法测试页面
     *
     * @param className className
     * @param fullMethodName sayHello(java.lang.String)
     * @param model model
     * @return method_test.html
     */
    @RequestMapping("method_test")
    public String methodTest(
            @RequestParam("className") String className,
            @RequestParam("fullMethodName") String fullMethodName,
            Model model) {
        List<InterfaceDTO> interfaceDTOS = interfaceContainer.findByInterfaceName(className);
        if (null != interfaceDTOS && !interfaceDTOS.isEmpty()) {
            model.addAttribute("interfaces", interfaceDTOS);
        }
        model.addAttribute("className", className);
        model.addAttribute("fullMethodName", fullMethodName);
        return "method_test";
    }

    /**
     * 类方法详情列表页面
     *
     * @param className className
     * @param model model
     * @return methods.html
     */
    @RequestMapping("methods")
    public String methods(@RequestParam("className") String className, Model model) {
        List<InterfaceDTO> interfaceDTOS = interfaceContainer.findByInterfaceName(className);
        if (null != interfaceDTOS && !interfaceDTOS.isEmpty()) {
            InterfaceDTO interfaceDTO = interfaceDTOS.get(0);
            model.addAttribute("data", interfaceDTO);
        }
        model.addAttribute("className", className);
        return "methods";
    }

    @RequestMapping()
    public String index(
            @RequestParam(value = "name", defaultValue = "", required = false) String name,
            Model model) {
        List<InterfaceDTO> interfaceDTOS = interfaceContainer.findContainsByInterfaceName(name);
        Set<String> unregNodeContainerAll = unregNodeContainer.findAll();
        TreeSet<String> allInterfaces = new TreeSet<>(String::compareToIgnoreCase);
        allInterfaces.addAll(unregNodeContainerAll);
        interfaceDTOS.forEach(
                it -> {
                    allInterfaces.add(it.getClassName());
                });
        model.addAttribute("interfaces", allInterfaces);
        return "index";
    }
}
